﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EntModApp.EntServices.Data;
using System.Data.Entity;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;
using System.Data.Entity.Infrastructure;
using System.Data;

namespace EntModApp.EntServices.Data
{
  public abstract class DbContextDataRepository<TContext, TEntity, TQuery>
    : IDataRepository<TEntity, TQuery>
    where TContext : DbContext
    where TEntity : class
    where TQuery : class
  {
    protected DbContextDataRepository(TContext context, DbSet<TEntity> dataSet, bool ownContext)
    {
      Contract.Requires(context != null, "context != null");
      Contract.Requires(dataSet != null, "dataSet != null");

      Contract.Ensures(!IsDisposed, "!IsDisposed");
      Contract.Ensures(OwnContext == ownContext, "OwnContext == ownContext");
      Contract.Ensures(Context == context, "Context == context");
      Contract.Ensures(DataSet == dataSet, "DataSet == dataSet");

      OwnContext = ownContext;
      Context = context;
      DataSet = dataSet;
    }

    public bool OwnContext { get; private set; }

    public TContext Context { get; private set; }

    public DbSet<TEntity> DataSet { get; private set; }


    public virtual TEntity New()
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(Contract.Result<TEntity>() != null, "result != null");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      var result = DataSet.Create();
      return result;
    }

    //public TDerived New<TDerived>() where TDerived : class, TEntity
    //{
    //  Contract.Requires(!IsDisposed, "!IsDisposed");

    //  Contract.Ensures(Contract.Result<TEntity>() != null, "result != null");
    //  Contract.Ensures(!IsDisposed, "!IsDisposed");

    //  var result = DataSet.Create<TDerived>();
    //  return result;
    //}

    public virtual TEntity Create(TEntity t)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(t != null, "t != null");

      Contract.Ensures(Contract.Result<TEntity>() != null, "result != null");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      var result = DataSet.Add(t);
      SaveIfOwned();
      return result;
    }

    public virtual void Delete(TEntity t)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(t != null, "t != null");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      Context.Entry<TEntity>(t);
      DataSet.Remove(t);
      SaveIfOwned();
    }

    public virtual TEntity Update(TEntity t)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      var entry = Context.Entry<TEntity>(t);
      DataSet.Attach(t);
      entry.State = EntityState.Modified;
      SaveIfOwned();
      return t;
    }

    public virtual void Commit()
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      Context.SaveChanges();
    }

    public void Dispose()
    {
      if (IsDisposed) return;
      IsDisposed = true;
      if (this.OwnContext)
        this.Context.Dispose();
      GC.SuppressFinalize(this);
    }

    public bool IsDisposed { get; private set; }

    protected int SaveIfOwned()
    {
      Contract.Assert(!IsDisposed, "!IsDisposed");

      if (OwnContext)
        return Context.SaveChanges();
      return 0;
    }


    public abstract IEnumerable<TEntity> RetrieveAll(out int total, bool retrieveTotal);

    public IEnumerable<TEntity> RetrieveAll()
    {
      int total;
      return RetrieveAll(out total, false);
    }

    public abstract IEnumerable<TEntity> Retrieve(TQuery query, out int total);

    public abstract int Count();

    public abstract int Count(TQuery query);
  }

  public abstract class DbContextDataRepository<TContext, TEntity, TDerived, TQuery>
    : IDataRepository<TDerived, TQuery>
    where TContext : DbContext
    where TEntity : class
    where TDerived : class, TEntity
    where TQuery : class
  {
    protected DbContextDataRepository(TContext context, DbSet<TEntity> dataSet, bool ownContext)
    {
      Contract.Requires(context != null, "context != null");
      Contract.Requires(dataSet != null, "dataSet != null");

      Contract.Ensures(!IsDisposed, "!IsDisposed");
      Contract.Ensures(OwnContext == ownContext, "OwnContext == ownContext");
      Contract.Ensures(Context == context, "Context == context");
      Contract.Ensures(DataSet == dataSet, "DataSet == dataSet");

      OwnContext = ownContext;
      Context = context;
      DataSet = dataSet;
    }

    public bool OwnContext { get; private set; }

    public TContext Context { get; private set; }

    public DbSet<TEntity> DataSet { get; private set; }


    public virtual TDerived New()
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(Contract.Result<TEntity>() != null, "result != null");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      var result = DataSet.Create<TDerived>();
      return result;
    }

    //public TDerived New<TDerived>() where TDerived : class, TEntity
    //{
    //  Contract.Requires(!IsDisposed, "!IsDisposed");

    //  Contract.Ensures(Contract.Result<TEntity>() != null, "result != null");
    //  Contract.Ensures(!IsDisposed, "!IsDisposed");

    //  var result = DataSet.Create<TDerived>();
    //  return result;
    //}

    public virtual TDerived Create(TDerived t)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(t != null, "t != null");

      Contract.Ensures(Contract.Result<TEntity>() != null, "result != null");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      var result = (TDerived)DataSet.Add(t);
      SaveIfOwned();
      return result;
    }

    public virtual void Delete(TDerived t)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(t != null, "t != null");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      Context.Entry<TEntity>(t);
      DataSet.Remove(t);
      SaveIfOwned();
    }

    public virtual TDerived Update(TDerived t)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      var entry = Context.Entry<TEntity>(t);
      DataSet.Attach(t);
      entry.State = EntityState.Modified;
      SaveIfOwned();
      return t;
    }

    public virtual void Commit()
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      Context.SaveChanges();
    }

    public void Dispose()
    {
      if (IsDisposed) return;
      IsDisposed = true;
      if (this.OwnContext)
        this.Context.Dispose();
      GC.SuppressFinalize(this);
    }

    public bool IsDisposed { get; private set; }

    protected int SaveIfOwned()
    {
      Contract.Assert(!IsDisposed, "!IsDisposed");

      if (OwnContext)
        return Context.SaveChanges();
      return 0;
    }


    public abstract IEnumerable<TDerived> RetrieveAll(out int total, bool retrieveTotal);

    public IEnumerable<TDerived> RetrieveAll()
    {
      int total;
      return RetrieveAll(out total, false);
    }

    public abstract IEnumerable<TDerived> Retrieve(TQuery query, out int total);

    public abstract int Count();

    public abstract int Count(TQuery query);
  }
}
